﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;
using System.ComponentModel;

namespace YasharEl.Infrastructure.PresentationModel.ComponentModel
{
    /// <summary>
    /// A base class for property change notification.
    /// <seealso cref="PropertyChangeNotifier"/>.
    /// </summary>
    [Serializable]
    public abstract class NotifyPropertyChangeBase : INotifyPropertyChanged, INotifyPropertyChanging
    {
        [field: NonSerialized]
        PropertyChangeNotifier notifier;

        readonly bool useExtendedEventArgs = true;

        /// <summary>
        /// Gets the notifier. It is lazy loaded.
        /// </summary>
        /// <value>The notifier.</value>
        protected PropertyChangeNotifier Notifier
        {
            get
            {
                /* We use lazy instantiation because hooking up the events 
                 * for many instances is expensive. */
                if (notifier == null)
                {
                    InitializePropertyChangeNotifier();
                }
                return notifier;
            }
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        void InitializePropertyChangeNotifier()
        {
            if (notifier == null)
            {
                notifier = new PropertyChangeNotifier(this, useExtendedEventArgs);
            }
        }

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        protected virtual void OnPropertyChanged(string propertyName)
        {
            Notifier.NotifyChanged(propertyName);
        }

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="propertyExpression">An expression to retrieve the property. E.g., () => Property </param>
        /// <example>OnPropertyChanged(() => Name);</example>
        protected virtual void OnPropertyChanged<TProperty>(Expression<Func<TProperty>> propertyExpression)
        {
            Notifier.NotifyChanged(propertyExpression);
        }

        /// <summary> 
        /// Assigns the specified newValue to the specified property
        /// and then notifies listeners that the property has changed.
        /// Assignment nor notification will occur if the specified
        /// property and newValue are equal. 
        /// Not recommended for frequent operations 
        /// as evaluation of the property expression is slow.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <typeparam name="TField">The type of the field. 
        /// When assignment occurs, a downcast is applied.</typeparam>
        /// <param name="propertyExpression">The expression that is used to derive the property name.
        /// Should not be <code>null</code>.</param>
        /// <param name="field">A reference to the property that is to be assigned.</param>
        /// <param name="newValue">The value to assign the property.</param>
        /// <exception cref="ArgumentNullException">
        /// Occurs if the specified propertyName is <code>null</code>.</exception>
        /// <exception cref="ArgumentException">
        /// Occurs if the specified propertyName is an empty string.</exception>
        protected AssignmentResult Assign<TProperty, TField>(
            Expression<Func<TProperty>> propertyExpression, ref TField field, TField newValue)
            where TField : TProperty
        {
            return Notifier.Assign<TProperty, TField>(propertyExpression, ref field, newValue);
        }

        /// <summary>
        /// Assigns the specified newValue to the specified property
        /// and then notifies listeners that the property has changed.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="propertyName">Name of the property. Can not be null.</param>
        /// <param name="field">A reference to the property that is to be assigned.</param>
        /// <param name="newValue">The value to assign the property.</param>
        /// <exception cref="ArgumentNullException">
        /// Occurs if the specified propertyName is <code>null</code>.</exception>
        /// <exception cref="ArgumentException">
        /// Occurs if the specified propertyName is an empty string.</exception>
        protected AssignmentResult Assign<TProperty>(
            string propertyName, ref TProperty field, TProperty newValue)
        {
            return Notifier.Assign(propertyName, ref field, newValue);
        }

        /// <summary>
        /// Assigns the specified newValue to the specified WeakReference field ref
        /// and then notifies listeners that the property has changed.
        /// Assignment nor notification will occur if the specified
        /// property and newValue are equal. 
        /// Uses an <see cref="Expression"/> to determine the property name, 
        /// which is slower than using the string property name overload.
        /// </summary>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="propertyName">The name of the property being changed.</param>
        /// <param name="fieldReference">A reference to the field <see cref="WeakReference"/> 
        /// that is to be assigned.</param>
        /// <param name="newValue">The value to assign the property.</param>
        /// <exception cref="ArgumentNullException">
        /// Occurs if the specified propertyName is <code>null</code>.</exception>
        /// <exception cref="ArgumentException">
        /// Occurs if the specified propertyName is an empty string.</exception>
        protected AssignmentResult Assign<TProperty>(
            string propertyName, ref WeakReference fieldReference, TProperty newValue)
            where TProperty : class
        {
            return Notifier.Assign(propertyName, ref fieldReference, newValue);
        }


        public NotifyPropertyChangeBase()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NotifyPropertyChangeBase"/> class.
        /// </summary>
        /// <param name="useExtendedEventArgs">if set to <c>true</c> 
        /// the PropertyChangeNotifier will use extended event args.</param>
        protected NotifyPropertyChangeBase(bool useExtendedEventArgs)
        {
            this.useExtendedEventArgs = useExtendedEventArgs;
        }

        #region Property change notification

        /// <summary>
        /// Occurs when a property value changes.
        /// <seealso cref="PropertyChangeNotifier"/>
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged
        {
            add
            {
                Notifier.PropertyChanged += value;
            }
            remove
            {
                Notifier.PropertyChanged -= value;
            }
        }

        /// <summary>
        /// Occurs when a property value is changing.
        /// <seealso cref="PropertyChangeNotifier"/>
        /// </summary>
        public event PropertyChangingEventHandler PropertyChanging
        {
            add
            {
                Notifier.PropertyChanging += value;
            }
            remove
            {
                Notifier.PropertyChanging -= value;
            }
        }

        #endregion
    }
}
